<?php
/**
 * Inter框架核心文件之缓存管理 - 文件方式
 * 缓存文件的生成和管理
 * 本组件依赖性：可独立使用。
 * 本文件改动自以下程序：
 *     - PHP框架DooPHP 1.2{@link http://www.doophp.com/}。头文件标识：
 * DooFileCache class file.
 * @author Leng Sheng Hong <darkredz@gmail.com>
 * @link http://www.doophp.com/
 * @copyright Copyright &copy; 2009 Leng Sheng Hong
 * @license http://www.doophp.com/license
 * 
 * 本文件的思路参考过以下程序，在此一并致谢！
 *     - PHP框架ThinkPHP 1.6Dev{@link http://www.thinkphp.cn/}
 * 
 * @author Horse Luke<horseluke@126.com>
 * @copyright Horse Luke, 2010
 * @license the Apache License, Version 2.0 (the "License"). {@link http://www.apache.org/licenses/LICENSE-2.0}
 * @version $Id: File.php 118 2010-07-04 06:17:18Z horseluke@126.com $
 * @package Inter_PHP_Framework
 */

class Inter_Cache_File{

    /**
     * 存放缓存文件的目录，末尾不要有/
     * 设置方法：可通过创建对象的时候批量传值，或者使用$this->setOptions(array('cacheDir' => 'xxxxx'))方法实现
     * @var string
     */
    protected $_cacheDir = null;


    /**
     * 缓存文件的后缀
     * 设置方法：可通过创建对象的时候批量传值，或者使用$this->setOptions( array('suffix' => 'xxxxx') )方法实现
     * @var string
     */
    protected $_suffix = '';

    /**
     * 前置内容，放置于缓存文件内容的最前面。
     * 使用场景：当属性$_suffix被设置为'.php'时，可在此设置为'<?php exit(); ?>'，以确保虚拟主机不会泄露缓存内容。
     * 设置方法：可通过创建对象的时候批量传值，或者使用$this->setOptions( array('preposedContent' => 'xxxxx') )方法实现
     * @var string
     * 
     */

    protected $_preposedContent = null;


    /**
     * 构造方法
     * @param array $options （可选参数）本类各种属性（参数），其中数组索引中不需要在前面加下划线。
     */
    public function __construct( $options = array() ){
        if(!empty($options)){
            $this->setOptions( $options );
        }
    }

    /**
     * 魔术方法设置值。直接禁止。
     * @param string $name
     * @param mixed $value
     */
    public function __set($name, $value){
        //调用该魔术方法的只有可能是通过该类方法setOptions来完成，因此需要对$name的显示进行调整。
        $name = substr($name, 1);
        throw new Exception("Sorry but the object property '$name' can not found to set.");
    }

    
    /**
     * 批量设置本类属性（参数）
     * @param array $options 本类各种属性（参数），其中数组索引中不需要在前面加下划线。
     */
    public function setOptions( $options ){
        foreach( $options as $name => $value ){
            $name = '_'. $name;
            $this->$name = $value;
        }
    }

    /**
     * 获取本类各种属性（参数）。
     * @param string $name 参数名称，不需要在前面加下划线。
     */
    public function getOption( $name ){
        $name = '_'. $name;
        if(!isset($this->$name)){
            throw new Exception("Sorry but the object property '$name' not found");
        }else{
            return $this->$name;
        }
    }

    /**
     * 检查缓存目录是否正常
     * 请注意，本处必须自行保证当不传入$cacheDir的时候，$this->_cacheDir必须有值，否则将存在风险！
     * @param string $cacheDir （可选参数）缓存目录，结尾不要加/。如果不传值，则检查$this->_cacheDir。
     * @return mixed 检查提示。正常则为布尔值true，否则则返回错误的字段提示。
     *
     */
    public function checkCacheDir( $cacheDir = null ){
        if(empty($cacheDir)){
            $cacheDir = $this->_cacheDir;
        }

        if(!is_dir($cacheDir)){
            return "Cache Directory Setting is not valid!";
        }

        $testfilename = $cacheDir . DIRECTORY_SEPARATOR . 'test.txt';
        $result = file_put_contents($testfilename, 'test', LOCK_EX);
        if($result <= 0){
            return "Cache Directory is not writeable!";
        }

        $result = unlink($testfilename);
        if( false == $result ){
            return "Cache Directory is not deleteable!";
        }

        return true;
    }


    /**
     * 检查缓存目录的子目录是否正常。在检查前，如果该子目录不存在，则尝试创建之。
     * 请注意，本处必须自行保证$this->_cacheDir必须有值，否则将存在风险！
     * @param string $subDirname 缓存目录的子目录名，开头结尾均不要加"/"
     * @return mixed 检查提示。正常则为布尔值true，否则则返回错误的字段提示。
     *
     */
    public function checkCacheDirIn( $subDirname ){
        $cfile = $this->_cacheDir . DIRECTORY_SEPARATOR . $subDirname . DIRECTORY_SEPARATOR;
        if(!file_exists($cfile)){
            mkdir($cfile);
        }

        return $this->checkCacheDir($this->_cacheDir . DIRECTORY_SEPARATOR . $subDirname );
    }

    /**
     * 获取文件缓存内容（整体改动自DooPHP同名函数）
     * @param string $id 文件缓存的唯一标识
     * @return mixed 文件缓存里面的数据。若缓存失效或不存在则返回空值(null)。
     */
    public function get($id){
        $cfile = $this->_cacheDir . DIRECTORY_SEPARATOR . md5($id) . $this->_suffix;
        return $this->_readCacheFile( $cfile );
    }


    /**
     * 在缓存目录的子目录中获取文件缓存内容（整体改动自DooPHP同名函数）
     * @param string $subDirname 缓存目录的子目录名，开头结尾均不要加"/"
     * @param string $id 文件缓存的唯一标识
     * @return mixed 文件缓存里面的数据。若缓存失效或不存在则返回空值(null)。
     */
    public function getIn($subDirname, $id) {
        $cfile = $this->_cacheDir . DIRECTORY_SEPARATOR . $subDirname . DIRECTORY_SEPARATOR . md5($id) . $this->_suffix;
        return $this->_readCacheFile( $cfile );
    }

    /**
      * 以一个唯一标识写入一个缓存（整体改动自DooPHP同名函数）
      * @param string $id 文件缓存的唯一标识
      * @param mixed $value 需要缓存的数据
      * @param int $expire 缓存时间，单位为秒。若为0，则表示永不过期。
      * @return integer 写入到文件内数据的字节数，0则为失败
      */
    public function set($id, $value, $expire=0) {
        $cfile = $this->_cacheDir . DIRECTORY_SEPARATOR . md5($id) . $this->_suffix;
        return $this->_writeCacheFile( $cfile, $value, $expire );
    }

    /**
     * 以一个唯一标识写入一个缓存到缓存的子目录（整体改动自DooPHP同名函数）
     * @param string $subDirname 缓存目录的子目录名，开头结尾均不要加"/"
     * @param string $id 文件缓存的唯一标识
     * @param mixed $value 需要缓存的数据
     * @param int $expire 缓存时间，单位为秒。若为0，则表示永不过期。
     * @return integer 写入到文件内数据的字节数，0则为失败
     */
    public function setIn($subDirname, $id, $value, $expire=0) {
        
        $cfile = $this->_cacheDir . DIRECTORY_SEPARATOR . $subDirname . DIRECTORY_SEPARATOR;
        if(!file_exists($cfile)){
            mkdir($cfile);
        }
        $cfile = $cfile. md5($id). $this->_suffix;
        
        return $this->_writeCacheFile( $cfile, $value, $expire );
    }

    /**
     * 删除一个缓存（整体改动自DooPHP函数flush）
     * 判断时返回值时候，请使用===号。
     * @param string $id 文件缓存的唯一标识
     * @return mixed 删除缓存结果。false表示缓存删除失败，true表示删除成功。-1表示根本不存在此缓存
     */
    public function delete($id) {
        $cfile = $this->_cacheDir . DIRECTORY_SEPARATOR . md5($id). $this->_suffix;
        if (file_exists($cfile)) {
            return unlink($cfile);
        }else{
            return -1;
        }
    }


    /**
     * 删除缓存子目录下的一个缓存（整体改动自DooPHP函数flushIn）
     * @param string $subDirname 缓存目录的子目录名，开头结尾均不要加"/"
     * @param string $id 文件缓存的唯一标识
     * @return mixed 删除缓存结果。false表示缓存删除失败，true表示删除成功。-1表示根本不存在此缓存
     */
    public function deleteIn($subDirname, $id){
        $cfile = $this->_cacheDir . DIRECTORY_SEPARATOR . $subDirname . DIRECTORY_SEPARATOR . md5($id). $this->_suffix;
        if (file_exists($cfile)) {
            return unlink($cfile);
        }else{
            return -1;
        }
    }


    /**
     * 删除缓存根目录下的所有缓存（整体改动自DooPHP函数flushAll）
     * 返回值总为true
     * @param bool $force 是否强制删除？如果为false，则只删除过期的文件；否则将强制删除
     * @return bool 总为true
     */
    public function deleteAll( $force = true ) {
        $handle = opendir($this->_cacheDir . DIRECTORY_SEPARATOR);

        while(($file = readdir($handle)) !== false) {
            $file = $this->_cacheDir . DIRECTORY_SEPARATOR . $file;
            if (is_file($file)){
                if( true == $force ){
                    unlink($file);
                }else{
                    $this->_readCacheFile( $file );
                }
            }
        }
        closedir($handle);
        return true;
    }


    /**
     * 删除缓存子目录下的所有缓存（整体改动自DooPHP函数flushAllIn）
     * 返回值总为true
     * @param string $subDirname 缓存目录的子目录名，开头结尾均不要加"/"
     * @param bool $force 是否强制删除？如果为false，则只删除过期的文件；否则将强制删除
     * @return bool 总为true
     */
    public function deleteAllIn($subDirname, $force){
        $cacheSubDir = $this->_cacheDir . DIRECTORY_SEPARATOR . $subDirname . DIRECTORY_SEPARATOR;
        if(is_dir($cacheSubDir)){
            $handle = opendir($cacheSubDir);
            while(($file = readdir($handle)) !== false) {
                $file = $cacheSubDir. $file;
                if (is_file($file)){
                    if( true == $force ){
                        unlink($file);
                    }else{
                        $this->_readCacheFile( $file );
                    }
                }
            }
            closedir($handle);
        }
        return true;
    }
    
    
    /**
     * （protected属性）根据文件路径，读取文件缓存内容
     *
     * @param string $filepath 文件缓存路径
     * @return mixed 文件缓存里面的数据。若缓存失效或不存在则返回空值(null)。
     */
    protected function _readCacheFile( $filepath ){
        if (file_exists($filepath)){
            $data = file_get_contents($filepath) ;
            if(!is_null($this->_preposedContent)){
                $data = str_replace( $this->_preposedContent, '', $data );
            }
            
            $expire = (int)substr($data, 0, 12);
            if( 0 === $expire || time() < $expire){
                return unserialize(substr($data, 12));
            }else{
                unlink($filepath);
            }
        }
        
        return null;
    }
    
    
    /**
      * （protected属性）根据文件路径，写入一个缓存
      *
      * @param string $filepath 文件缓存路径
      * @param mixed $data 需要缓存的数据
      * @param int $expire 缓存时间，单位为秒。若为0，则表示永不过期。
      * @return integer 写入到文件内数据的字节数，0则为失败
      */
    protected function _writeCacheFile( $filepath, $data, $expire = 0 ){
        if( 0 === $expire ){
            $expire = 0;
        }else{
            $expire = time()+$expire;
        }
        $expire = sprintf('%012d', $expire);    //本灵感来源于ThinkPHP
        
        $data = $expire. serialize($data);
        if(!is_null($this->_preposedContent)){
            $data = $this->_preposedContent. $data ;
        }
        return file_put_contents($filepath, $data, LOCK_EX);
    }

}